import abc
from dtrader.dataseries import collections


DEFAULT_MAX_LEN = 1024


def get_checked_max_len(max_len):
    if max_len is None:
        max_len = DEFAULT_MAX_LEN
    if not max_len > 0:
        raise Exception("Invalid maximum length")
    return max_len


# It is important to inherit object to get __getitem__ to work properly.
# Check http://code.activestate.com/lists/python-list/621258/
class DataSeries(object):
    """Base class for data series.
    .. note::
        This is a base class and should not be used directly.
    """

    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def __len__(self):
        """Returns the number of elements in the data series."""
        raise NotImplementedError()

    def __getitem__(self, key):
        """Returns the value at a given position/slice. It raises IndexError if the position is invalid,
        or TypeError if the key type is invalid."""
        if isinstance(key, slice):
            return [self[i] for i in xrange(*key.indices(len(self)))]
        elif isinstance(key, int):
            if key < 0:
                key += len(self)
            if key >= len(self) or key < 0:
                raise IndexError("Index out of range")
            return self.get_value_absolute(key)
        else:
            raise TypeError("Invalid argument type")

    # This is similar to __getitem__ for ints, but it shouldn't raise for invalid positions.
    @abc.abstractmethod
    def get_value_absolute(self, pos):
        raise NotImplementedError()

    @abc.abstractmethod
    def get_date_times(self):
        """Returns a list of :class:`date_times.date_times` associated with each value."""
        raise NotImplementedError()


class SequenceDataSeries(DataSeries):
    """A DataSeries that holds values in a sequence in memory.

    :param max_len: The maximum number of values to hold.
        Once a bounded length is full, when new items are added, a corresponding number of items are discarded from the opposite end.
    :type max_len: int.
    """

    def __init__(self, max_len=None):
        super(SequenceDataSeries, self).__init__()
        max_len = get_checked_max_len(max_len)

        self.__values = collections.ListDeque(max_len)
        self.__date_times = collections.ListDeque(max_len)

    def __len__(self):
        return len(self.__values)

    def __getitem__(self, key):
        return self.__values[key]

    def set_max_len(self, max_len):
        """Sets the maximum number of values to hold and resizes accordingly if necessary."""
        self.__values.resize(max_len)
        self.__date_times.resize(max_len)

    def get_max_len(self):
        """Returns the maximum number of values to hold."""
        return self.__values.get_max_len()

    def get_value_absolute(self, pos):
        ret = None
        if pos >= 0 and pos < len(self.__values):
            ret = self.__values[pos]
        return ret

    def append(self, value):
        """Appends a value."""
        self.append_with_date_time(None, value)

    def append_with_date_time(self, date_times, value):
        """
        Appends a value with an associated date_times.

        .. note::
            If date_times is not None, it must be greater than the last one.
        """

        if date_times is not None and len(self.__date_times) != 0 and self.__date_times[-1] >= date_times:
            raise Exception("Invalid date_times. It must be bigger than that last one")

        assert(len(self.__values) == len(self.__date_times))
        self.__date_times.append(date_times)
        self.__values.append(value)

    def get_date_times(self):
        return self.__date_times.data()

    def get_last_date_time(self):
        if len(self.__date_times) != 0:
            return self.__date_times[-1]
        else:
            return None
